﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace NetworkingLibrary.Servers.Specialised
{
    public partial class GameServer : IServer
    {
        private Combined.UDPTCPCombinedServer TheCombinedServer = null;

        public event Events.ClientConnectDelegate OnClientConnect = delegate { };
        public event Events.ClientDisconnectDelegate OnClientDisconnect = delegate { };
        public event Events.MaxConnectionsDelegate OnMaxConnections = delegate { };
        public event Events.MessageReceivedDelegate OnMessageReceived = delegate { };
        public event Events.ErrorDelegate OnError = delegate { };

        public int MaxFlowRate
        {
            get
            {
                return TheCombinedServer.MaxFlowRate;
            }
            set
            {
                TheCombinedServer.MaxFlowRate = value;
            }
        }
        public DateTime LastSendTime
        {
            get
            {
                return TheCombinedServer.LastSendTime;
            }
            set
            {
                TheCombinedServer.LastSendTime = value;
            }
        }
        
        public int BufferSize
        {
            get
            {
                return TheCombinedServer.BufferSize;
            }
            set
            {
                TheCombinedServer.BufferSize = value;
            }
        }
        public bool Initialised
        {
            get
            {
                return TheCombinedServer.Initialised;
            }
        }
        public bool Terminating
        {
            get
            {
                return TheCombinedServer.Terminating;
            }
            set
            {
                TheCombinedServer.Terminating = value;
            }
        }

        public int MaxConnections
        {
            get
            {
                return TheCombinedServer.MaxConnections;
            }
            set
            {
                TheCombinedServer.MaxConnections = value;
            }
        }

        public System.Net.IPAddress LocalAddress
        {
            get
            {
                return Dns.GetHostAddresses(Dns.GetHostName()).First();
            }
        }

        public Dictionary<Guid, Clients.IClient> AllClients
        {
            get
            {
                return TheCombinedServer.AllClients;
            }
        }

        Clients.Specialised.ManagerServerClient MSClient = null;

        public int UDPMulticastTTL = 1;

        public GameServer(int TCPPort, int UDPPort, string TCPAddr, IPAddress UDPMulticastAddress, int MaximumConnections)
        {
            TheCombinedServer = new Combined.UDPTCPCombinedServer();
            Init();

            if (!Start(TCPPort, UDPPort, TCPAddr, MaximumConnections, UDPMulticastAddress))
            {
                throw new Exceptions.ServerStartException();
            }
        }
        public GameServer(int TCPPort, int UDPPort, string TCPAddr, IPAddress UDPMulticastAddress, IPAddress ManagerServerAddress, int ManagerServerPort, int MaximumConnections)
        {
            MSClient = new Clients.Specialised.ManagerServerClient(ManagerServerAddress, ManagerServerPort);

            if (MSClient.Connected)
            {
                if (MSClient.AddServer(LocalAddress, UDPMulticastAddress))
                {
                    TheCombinedServer = new Combined.UDPTCPCombinedServer();

                    Init();

                    if (!Start(TCPPort, UDPPort, TCPAddr, MaximumConnections, UDPMulticastAddress))
                    {
                        throw new Exceptions.ServerStartException();
                    }
                }
                else
                {
                    throw new Exceptions.ServerStartException("Could not add this machine to manager server's servers list.");
                }
            }
            else
            {
                throw new Exceptions.ServerStartException("Could not connect to manager server.");
            }
        }
        public GameServer(int TCPPort, int UDPPort, string TCPAddr, IPAddress UDPMulticastAddress, Clients.Specialised.ManagerServerClient AnMSClient, int MaximumConnections)
        {
            MSClient = AnMSClient;

            if (MSClient.Connected)
            {
                TheCombinedServer = new Combined.UDPTCPCombinedServer();
                Init();

                if (!Start(TCPPort, UDPPort, TCPAddr, MaximumConnections, UDPMulticastAddress))
                {
                    throw new Exceptions.ServerStartException();
                }
            }
            else
            {
                throw new Exceptions.ServerStartException("MSClient not connected!");
            }
        }

        private void Init()
        {
            TheCombinedServer.OnClientConnectTCP += new Events.ClientConnectDelegate(TheCombinedServer_OnClientConnectTCP);
            TheCombinedServer.OnClientDisconnectTCP += new Events.ClientDisconnectDelegate(TheCombinedServer_OnClientDisconnectTCP);
            TheCombinedServer.OnError += new Events.ErrorDelegate(TheCombinedServer_OnError);
            TheCombinedServer.OnMaxConnections += new Events.MaxConnectionsDelegate(TheCombinedServer_OnMaxConnections);
            TheCombinedServer.OnMessageReceived += new Events.MessageReceivedDelegate(TheCombinedServer_OnMessageReceived);
        }

        private void TheCombinedServer_OnMessageReceived(Events.MessageReceivedEventArgs e)
        {
            try
            {
                Type MessageType = e.TheMessage.GetType();
                                
                OnMessageReceived.Invoke(e);
                if (!e.PreventDefault)
                {
                    if (TheCombinedServer.TCPInitialised)
                    {
                        Send(e.TheMessage, e.TheClient.ID);
                    }
                    else
                    {
                        SendUDP(e.TheMessage, e.TheClient.ID);
                    }
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }
        
        private void TheCombinedServer_OnMaxConnections(Events.MaxConnectionsEventArgs e)
        {
            try
            {
                MSClient.RemoveServer(TheCombinedServer.LocalAddress, true);
                OnMaxConnections.Invoke(e);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }
        private void TheCombinedServer_OnClientDisconnectTCP(Events.ClientDisconnectEventArgs e)
        {
            try
            {
                if (AllClients.Count == MaxConnections - 1)
                {
                    MSClient.AddServer(TheCombinedServer.LocalAddress, TheCombinedServer.udpMulticastAddress, true);
                }
                OnClientDisconnect.Invoke(e);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }
        private void TheCombinedServer_OnClientConnectTCP(Events.ClientConnectEventArgs e)
        {
            OnClientConnect.Invoke(e);
        }
        
        private void TheCombinedServer_OnError(Events.ErrorEventArgs e)
        {
            OnError.Invoke(e);
        }
        
        public bool Initialise(int Port, string Addr)
        {
            return TheCombinedServer.Initialise(Port, Addr);
        }
        public bool Start(int TCPPort, string Addr, int? MaxConnections = null)
        {
            return TheCombinedServer.Start(TCPPort, Addr, MaxConnections);
        }
        public bool Start(int TCPPort, int UDPPort, string TCPAddr, int MaxConnections, IPAddress UDPMulticastAddress)
        {
            return TheCombinedServer.StartBoth(TCPPort, UDPPort, TCPAddr, MaxConnections, UDPMulticastAddress, UDPMulticastTTL);
        }
        public bool Stop()
        {
            bool OK = MSClient != null ? MSClient.Disconnect() : true;

            return OK && TheCombinedServer.Stop();
        }

        public bool Send(byte[] TheBuffer)
        {
            return TheCombinedServer.Send(TheBuffer);
        }
        public bool SendTo(byte[] TheBuffer, Guid ToID)
        {
            return TheCombinedServer.SendTo(TheBuffer, ToID);
        }
        public bool Send(byte[] TheBuffer, Guid ExcludeID)
        {
            return TheCombinedServer.Send(TheBuffer, ExcludeID);
        }
        public bool Send(Messages.MessageObject TheMessage)
        {
            return TheCombinedServer.Send(TheMessage);
        }
        public bool SendTo(Messages.MessageObject TheMessage, Guid ToId)
        {
            return TheCombinedServer.SendTo(TheMessage, ToId);
        }
        public bool Send(Messages.MessageObject TheMessage, Guid ExcludeID)
        {
            return TheCombinedServer.Send(TheMessage, ExcludeID);
        }

        public bool SendUDP(byte[] TheBuffer)
        {
            return TheCombinedServer.SendUDP(TheBuffer);
        }
        public bool SendUDPTo(byte[] TheBuffer, Guid ToID)
        {
            return TheCombinedServer.SendToUDP(TheBuffer, ToID);
        }
        public bool SendUDP(byte[] TheBuffer, Guid ExcludeID)
        {
            return TheCombinedServer.SendUDP(TheBuffer, ExcludeID);
        }
        public bool SendUDP(Messages.MessageObject TheMessage)
        {
            return TheCombinedServer.SendUDP(TheMessage);
        }
        public bool SendUDPTo(Messages.MessageObject TheMessage, Guid ToId)
        {
            return TheCombinedServer.SendToUDP(TheMessage, ToId);
        }
        public bool SendUDP(Messages.MessageObject TheMessage, Guid ExcludeID)
        {
            return TheCombinedServer.SendUDP(TheMessage, ExcludeID);
        }    
    }
}
